<?php
/**
 * HTTP响应类
 *
 * @category   ORG
 * @package    ORG
 * @subpackage RestClient
 * @author     海啸 <459103503@qq.com>
 */
class RestClient
{
    /**
     * 设置curl 基本参数
     *
     * @var array
     */
    public $curlOpts = [
        //获取的信息以文件流的形式返回，而不是直接输出
        CURLOPT_RETURNTRANSFER => true,
        //证书验证
        CURLOPT_SSL_VERIFYPEER => false,
        //curl会根据任何重定向命令更深层次的获取转向路径
        CURLOPT_FOLLOWLOCATION => false,
        //需要responseHEADER
        CURLOPT_HEADER=>true,
        //允许你定义跳转请求的最大次数
        CURLOPT_MAXREDIRS      => 10
    ];


    //发送请求的url
    protected $baseUrl;
    //最后一次的返回信息
    protected $lastResponse;
    //最后一次请求的信息
    protected $lastRequest;
    //最后一次请求的头信息
    protected $lastHeaders;
    //是否要扑捉异常
    protected $throwExceptions = true;
    //设置头信息
    protected $headers = array();

    /**
     * 构造函数
     *
     * @param string $baseUrl 发起请求的地址
     *
     * @throws RuntimeException
     */
    public function __construct($baseUrl)
    {
        //判断是否开启curl服务
        if (!function_exists('curl_init')) {
            throw new \RuntimeException('CURL module not available!');
        }
        //开启获取深层转向路劲返回的信息的功能
        if (ini_get('open_basedir') == '' && strtolower(ini_get('safe_mode')) == 'off') {
            $this->curlOpts[CURLOPT_FOLLOWLOCATION] = true;
        }

        $this->baseUrl = $baseUrl;
        $this->curlOpts[CURLOPT_HEADERFUNCTION] = array($this, '_handleHeader');
    }

    /**
     * 设置代理服务
     *
     * @param string $host Proxy host
     * @param string $port Proxy port
     * @param string $user User name
     * @param string $pass Password
     *
     * @return RestClient
     */
    public function setupProxy($host, $port, $user = null, $pass = null)
    {
        $this->curlOpts[CURLOPT_PROXYTYPE] = 'HTTP';
        $this->curlOpts[CURLOPT_PROXY] = $host;
        $this->curlOpts[CURLOPT_PROXYPORT] = $port;
        if ($user && $pass) {
            $this->curlOpts[CURLOPT_PROXYUSERPWD] = $user . ":" . $pass;
        }

        return $this;
    }

    /**
     * 发送HTTP GET请求
     *
     * @param string $url     资源url
     * @param array  $data    提交参数
     * @param array  $headers 头信息
     *
     * @return string
     */
    public function get($url, $data=array(), $headers=array())
    {
        if (!empty($data)) {
            $pos = strpos($url, '?');
            if ($pos !== false) {
                $url = substr($url, 0, $pos);
            }
            $url .= '?' . http_build_query($data);
        }

        //设置头信息
        $this->_setHeaders($headers);
        $this->_setHttpHeaders();

        $curl = $this->prepRequest($this->curlOpts, $url);
        $body = $this->_doRequest($curl);
        return $body;
    }

    /**
     * 发送HTTP HEAD请求
     *
     * @param string $url 资源url
     *
     * @return string
     */
    public function head($url)
    {
        $curlOpts = $this->curlOpts;

        //启用时将不对HTML中的body部分进行输出。
        $curlOpts[CURLOPT_NOBODY] = true;

        $curl = $this->prepRequest($this->curlOpts, $url);
        $body = $this->_doRequest($curl);
        return $body;
    }

    /**
     * 发送HTTP POST
     *
     * @param string $url     资源URL
     * @param mixed  $data    提交数据
     * @param array  $headers 头信息
     *
     * @return string
     */
    public function post($url, $data, $headers=array())
    {
        $data = $this->_prepData($data);

        //设置操作类型
        $this->_setMethod('POST');

        //设置头信息
        $this->_setHeaders($headers, $data);
        $this->_setHttpHeaders();

        //提交的数据
        $this->_setHttpData($data);

        $curl = $this->prepRequest($this->curlOpts, $url);
        $body = $this->_doRequest($curl);
        return $body;
    }

    /**
     * 发送HTTP PUT请求
     *
     * @param string $url     资源URL
     * @param mixed  $data    提交的数据
     * @param array  $headers 头信息
     *
     * @return string
     */
    public function put($url, $data, $headers=array())
    {
        $data = $this->_prepData($data);

        //设置操作类型
        $this->_setMethod('PUT');

        //设置头信息
        $this->_setHeaders($headers, $data);
        $this->_setHttpHeaders();

        //提交的数据
        $this->_setHttpData($data);

        $curl = $this->prepRequest($this->curlOpts, $url);
        $body = $this->_doRequest($curl);

        return $body;
    }

    /**
     * 发送HTTP PATCH请求
     *
     * @param string $url     资源URL
     * @param mixed  $data    put形式提交的数据
     * @param array  $headers 头信息集合
     *
     * @return string
     */
    public function patch($url, $data, $headers=array())
    {
        $data = $this->_prepData($data);

        //设置操作类型
        $this->_setMethod('PATCH');

        //设置头信息
        $this->_setHeaders($headers, $data);
        $this->_setHttpHeaders();

        $curl = $this->prepRequest($this->curlOpts, $url);
        $body = $this->_doRequest($curl);

        return $body;
    }

    /**
     * 发送HTTP DELETE请求
     *
     * @param string $url     资源URL
     * @param array  $data    提交参数
     * @param array  $headers 头信息
     *
     * @return string
     */
    public function delete($url, $data=array(), $headers=array())
    {
        if (!empty($data)) {
            $pos = strpos($url, '?');
            if ($pos !== false) {
                $url = substr($url, 0, $pos);
            }
            $url .= '?' . http_build_query($data);
        }

        //设置头信息
        $this->_setHeaders($headers);
        $this->_setHttpHeaders();

        //设置操作类型
        $this->_setMethod('DELETE');

        $curl = $this->prepRequest($this->curlOpts, $url);
        $body = $this->_doRequest($curl);

        return $body;
    }

    /**
     * 设置头信息
     *
     * @param array $headers 头信息数据集
     * @param mixed $data    提交数据
     *
     * @return RestClient
     */
    private function _setHeaders($headers=array(), $data = null)
    {
        if (!is_array($data)) {
            $this->headers[] = 'Content-Length: '.strlen($data);
        }

        $this->headers = $this->_fHeaders($headers);

        return $this;
    }

    /**
     * 获取头信息
     *
     * @return array
     */
    public function getHeaders()
    {
        return $this->headers;

    }

    /**
     * 设置curl 头信息
     *
     * @return RestClient
     */
    private function _setHttpHeaders()
    {
        $this->curlOpts[CURLOPT_HTTPHEADER] = $this->headers;

        return $this;
    }

    /**
     * 设置提交数据
     *
     * @param mixed $data 提交的数据
     *
     * @return RestClient
     */
    private function _setHttpData($data)
    {
        $this->curlOpts[CURLOPT_POSTFIELDS] = $data;

        return $this;
    }

    /**
     * 设置HTTP 操作类型
     *
     * @param string $method 操作类型
     *
     * @return RestClient
     */
    private function _setMethod($method = 'GET')
    {
        $this->curlOpts[CURLOPT_CUSTOMREQUEST] = $method;

        return $this;
    }

    /**
     * 数据处理
     *
     * @param mixed $data 预提交数据
     *
     * @return string
     */
    private function _prepData($data)
    {
        if (is_array($data)) {
            $multiPart = false;

            foreach ($data as $item) {
                if (is_string($item) && strncmp($item, "@", 1) == 0 && is_file(substr($item, 1))) {
                    $multiPart = true;
                    break;
                }
            }

            return ($multiPart) ? $data : http_build_query($data);
        } else {
            return $data;
        }
    }

    /**
     * 组合预提交数据
     *
     * @param array  $opts CURL参数设置
     * @param string $url  资源URL
     *
     * @return object
     */
    protected function prepRequest($opts, $url)
    {
        if (strncmp($url, $this->baseUrl, strlen($this->baseUrl)) != 0) {
            $url = rtrim($this->baseUrl, '/') . '/' . ltrim($url, '/');
        }
        $curl = curl_init($url);

        foreach ($opts as $opt => $val) {
            curl_setopt($curl, $opt, $val);
        }

        $this->lastRequest = array(
            'url' => $url
        );

        if (isset($opts[CURLOPT_CUSTOMREQUEST])) {
            $this->lastRequest['method'] = $opts[CURLOPT_CUSTOMREQUEST];
        } else {
            $this->lastRequest['method'] = 'GET';
        }

        if (isset($opts[CURLOPT_POSTFIELDS])) {
            $this->lastRequest['data'] = $opts[CURLOPT_POSTFIELDS];
        }

        return $curl;
    }

    /**
     * CURL发送请求
     *
     * @param object $curl CURL对象
     *
     * @return mixed
     */
    private function _doRequest($curl)
    {
        $this->lastHeaders = array();

        $body = curl_exec($curl);
        $meta = curl_getinfo($curl);

        $this->lastResponse = array(
            'body' => $body,
            'meta' => $meta
        );

        curl_close($curl);

        $this->checkLastResponseForError();

        return $this->getRsData($body);
    }

    /**
     * Check Last Response Error
     *
     * @return void
     * @throws RestException
     */
    protected function checkLastResponseForError()
    {
        if ( !$this->throwExceptions) {
            return;
        }

        $meta = $this->lastResponse['meta'];
        $body = $this->lastResponse['body'];

        if (!$meta) {
            return;
        }
        $err = null;
        switch ($meta['http_code']) {
        case 200:
            return;
            break;
        case 201:
            return;
            break;
        case 400:
             $code = RestException::BAD_REQUEST;
            break;
        case 401:
             $code = RestException::UNAUTHORIZED;
            break;
        case 403:
             $code = RestException::FORBIDDEN;
            break;
        case 404:
             $code = RestException::NOT_FOUND;
            break;
        case 405:
             $code = RestException::METHOD_NOT_ALLOWED;
            break;
        case 409:
             $code = RestException::CONFLICT;
            break;
        case 410:
             $code = RestException::GONE;
            break;
        case 422:
             $code = RestException::INVALID_RECORD;
            break;
        default:
            if ($meta['http_code'] >= 400 && $meta['http_code'] <= 499) {
                $code = RestException::CLIENT_ERROR;
            } elseif ($meta['http_code'] >= 500 && $meta['http_code'] <= 599) {
                $code = RestException::SERVER_ERROR;
            } elseif (!$meta['http_code'] || $meta['http_code'] >= 600) {
                $code = RestException::UNKNOW_ERROR;
            }
        }

        throw new RestException($body, $code);
    }

    /**
     * 获取最后一次请求的body
     *
     * @return string
     */
    public function lastBody()
    {
        return $this->lastResponse['body'];
    }

    /**
     * 获取最后一次请求的状态
     *
     * @return number
     */
    public function lastStatus()
    {
        return $this->lastResponse['meta']['http_code'];
    }

    /**
     *获取最后一次请求的头信息
     *
     * @param string $header HTTP Header
     *
     * @return string
     */
    public function lastHeader($header)
    {
        if (empty($this->lastHeaders[strtolower($header)])) {
            return null;
        }
        return $this->lastHeaders[strtolower($header)];
    }

    /**
     * 使用回调函数获取头信息
     *
     * @param object $curl CURL资源
     * @param string $str  头信息字串
     *
     * @return int
     */
    private function _handleHeader($curl, $str)
    {
        if (preg_match('/([^:]+):\s(.+)/m', $str, $match) ) {
            $this->lastHeaders[strtolower($match[1])] = trim($match[2]);
        }
        return strlen($str);
    }

    /**
     * 格式化headers数据
     *
     * @param array $headers 头信息数据集
     *
     * @return array
     */
    private function _fHeaders($headers)
    {
        $hData = array();
        if (empty($headers)) {
            return $hData;
        }
        foreach ($headers as $key=>$value) {
            $hData[]=$key.':'.$value;
        }
        return $hData;
    }

    /**
     * 处理返回数据
     *
     * @param string $rsData 返回信息
     *
     * @return array
     */
    public function getRsData($rsData)
    {
        //定义返回数据
        $rs = array();
        $headers = array();

        list($header, $body) = explode("\r\n\r\n", $rsData, 2);

        $rs['body'] = (json_decode($body, true))?json_decode($body, true):$body;

        $headerArr = explode("\r\n", $header);
        foreach ($headerArr as $value) {
            if (!stripos($value, ':')) {
                list($protocol, $code, $status) = explode(' ', $value);
                $headers['protocol'] =$protocol;
                $headers['code'] =$code;
                $headers['status'] =$status;
            } else {
                list($key, $v) = explode(':', $value);
                $headers[$key] = $v;
            }

        }

        $rs['header'] = $headers;

        return $rs;
    }
}